﻿using System.Threading;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace VoiceShieldCS
{
    /// <summary>
    /// Driver for VoiceShield by SpikenzieLabs.com
    /// </summary>
    public class VoiceShield
    {
        #region Constants and Enums

        //ISD Commands
        enum ISDCOMMAND
        {
            POWERUP = 4,
            SETPLAY = 7,
            SETREC = 5,
            STOP = 12
        }

        //Setup CPU Pins this matches what the VoiceShield hardware expects
        const Cpu.Pin SSPin = Pins.GPIO_PIN_D5;
        const Cpu.Pin MOSIPin = Pins.GPIO_PIN_D2;
        const Cpu.Pin SCLKPin = Pins.GPIO_PIN_D3;
        const Cpu.Pin INTUPin = Pins.GPIO_PIN_D4;

        #endregion

        #region Ports and Properties

        OutputPort SS;      //Select
        OutputPort MOSI;    //Master Out
        OutputPort SCLK;    //Clock
        InputPort INTU;     //Master In

        /// <summary>
        /// Number of Sound Slots on ISD. Example 80 will create 3 second slots
        /// </summary>
        public byte SoundSlots { get; private set; }

        #endregion

        #region Private Methods and Constructor

        /// <summary>
        /// Constructor for VoiceShield
        /// You provide the number of Sound Slots you have on the ISD
        /// Total length per slot is 240 seconds / sound slots = total per slot
        /// </summary>
        /// <param name="SoundSlots">Number of Sound Slots on the device</param>
        public VoiceShield(byte SoundSlots)
        {
            this.SoundSlots = SoundSlots;
            SS = new OutputPort(SSPin, true);
            MOSI = new OutputPort(MOSIPin, false);
            SCLK = new OutputPort(SCLKPin, false);
            INTU = new InputPort(INTUPin, false, Port.ResistorMode.Disabled);
        }

        /// <summary>
        /// Send message by SPI to VoiceShield
        /// </summary>
        /// <param name="SPIbits">8 or 16 bit message</param>
        /// <param name="ISDcont">ISD Command</param>
        /// <param name="SlotNumb">Sound Slot Number</param>
        private void SPI_TX(byte SPIbits, ISDCOMMAND ISDcont, byte SlotNumb)
        {
            int ISDAddress = ((SlotNumb) * (1200 / SoundSlots));
            byte cmd = (byte)ISDcont;
            uint testTemp = 0;
            uint ISDMessage = 0;

            if (SPIbits == 16) //16 bit message
            {
                for (int i = 1; i < 12; i++)
                {
                    testTemp = (uint)(1 & ISDAddress);
                    ISDMessage = ISDMessage | testTemp;
                    if (i < 11)
                    {
                        ISDMessage = ISDMessage << 1;
                        ISDAddress = ISDAddress >> 1;
                    }
                }

                ISDMessage = ISDMessage << 1;

                for (int i = 1; i < 6; i++)
                {
                    testTemp = (uint)(16 & cmd);
                    testTemp = testTemp >> 4;
                    ISDMessage = ISDMessage | testTemp;
                    if (i < 5)
                    {
                        ISDMessage = ISDMessage << 1;
                        cmd = (byte)(cmd << 1);
                    }
                }
            }
            else //8 bit message
            {
                for (int i = 1; i < 6; i++)
                {
                    testTemp = (uint)(16 & cmd);
                    testTemp = testTemp >> 4;
                    ISDMessage = ISDMessage | testTemp;
                    if (i < 5)
                    {
                        ISDMessage = ISDMessage << 1;
                        cmd = (byte)(cmd << 1);
                    }
                }
            }

            SS.Write(true);
            SCLK.Write(false);

            SS.Write(false);
            Thread.Sleep(5);

            for (int i = 1; i < (SPIbits + 1); i++)
            {
                testTemp = (ISDMessage >> (SPIbits - 1));
                testTemp = testTemp & 1;
                if (testTemp == 0)
                {
                    MOSI.Write(false);
                }
                else
                {
                    MOSI.Write(true);
                }

                SCLK.Write(true);
                Thread.Sleep(5);
                SCLK.Write(false);
                ISDMessage = ISDMessage << 1;
            }

            SS.Write(true);
            SCLK.Write(false);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Record via audio in to addressed sound slot
        /// Recording will continue until memory is out or stop is sent
        /// </summary>
        /// <param name="VS_Address">Id of slot to record to</param>
        public void ISDRecord(byte VS_Address)
        {
            SPI_TX(8, ISDCOMMAND.POWERUP, 0);
            Thread.Sleep(25);
            SPI_TX(8, ISDCOMMAND.POWERUP, 0);
            Thread.Sleep(50);
            SPI_TX(16, ISDCOMMAND.SETREC, VS_Address);
        }

        /// <summary>
        /// Fires off sound at address and returns
        /// </summary>
        /// <param name="VS_Address">Id of slot to play</param>
        public void ISDPlay(byte VS_Address)
        {
            SPI_TX(8, ISDCOMMAND.POWERUP, 0);
            Thread.Sleep(25);
            SPI_TX(16, ISDCOMMAND.SETPLAY, VS_Address);
        }

        /// <summary>
        /// Fires off sound at address and waits for end to return
        /// </summary>
        /// <param name="VS_Address">Id of slot to play</param>
        public void ISDPlayToEOM(byte VS_Address)
        {
            var temp = false;
            SPI_TX(8, ISDCOMMAND.POWERUP, 0);
            Thread.Sleep(25);
            SPI_TX(16, ISDCOMMAND.SETPLAY, VS_Address);
            do
            {
                temp = INTU.Read();
            } while (temp == true);
        }

        /// <summary>
        /// Stops playback or recording
        /// </summary>
        public void ISDStop()
        {
            SPI_TX(8, ISDCOMMAND.STOP, 0);
            Thread.Sleep(50);
        }

        /// <summary>
        /// Returns if message is playing
        /// </summary>
        /// <returns>True: Message not playing</returns>
        public bool ISDEOM()
        {
            if (INTU.Read())
                return false;
            else
                return true;
        }

        #endregion
    }
}
